#!BPY
"""
Name: 'Sketch (.sk)...'
Blender: 248
Group: 'Export'
Tooltip: 'Export selected objects to the Sketch scene description language'
"""

__author__ = 'Kjell Magne Fauske'
__version__ = "1.0.1svn"
__url__ = ("Documentation, http://www.fauskes.net/code/blend2sketch/",
           "Author's homepage, http://www.fauskes.net/",
           "Sketch, http://www.frontiernet.net/~eugene.ressler/")


__bpydoc__ = """\
This script exports the selected objects to the Sketch scene description
language. Sketch is a small, simple system for producing line drawings of
two- or three-dimensional solid objects and scenes. Sketch outputs PGF/TikZ
or PSTricks code suitable for use in TeX/LaTeX documents. With this script
you can create objects in Blender and use them in your Sketch drawings.

Usage:

Select the objects you want to export and invoke the script from the
"File->Export" menu[1]. Alternatively you can load and run the script from
inside Blender.

Supported object types:<br>
    - Mesh<br>
    - Curve, Surf, MBall: Converted to mesh when exported<br>
    - Empties: Exported as a coordinates<br>
    - Material: Materials assigned to mesh and curve objects.<br>
    - Camera: The active camera will be used to set up a Sketch scene with a
viewpoint similar to Blender's.<br>

All drawable objects are exported as polygons or poly lines. Non-mesh objects
like curves and surfaces will be converted to a mesh when exported. Each face
in a mesh will be saved as a polygon. If the mesh does not has any faces,
edges will be saved as lines.

Materials:

The exporter has basic support for materials. By default the material's RGB
value is used as fill or draw color. An alternative is to specify polygon
and line options directly by putting the values in a 'polyoptions' and
'lineoptions' property assigned to the material. You can use the
Help->Property ID browser to set these values.

Empties:

Empties are used to place coordinates, labels and axes in the sketch code. Game
or object properties are used select what to draw. The following properties are recognized:

"type":<br>
    - axes<br>
    - special axes<br>
    - dotted axes<br>

"label"<br>
"style"<br>

Issues:

- Meshes with faces and edge-only parts are not supported yet. Only the faces
  will be exported in this case.<br>
- No support for parenting.<br>
- No support for fgons yet.<br>

[1] Requires you to put the script in Blender's scripts folder. Blender will
then automatically detect the script.
"""

# Changelog:
# SVN
#   - Minor code refactoring
#   - Added group support
#   - Added clipboard support
#   - Empties can now be used to insert labels/specials in the Sketch code
#   - Empties can be used do draw axes
#   - Cameras are exported as view transforms
#   - Text buffers can be used to store sketch code. Inserted in the generated
#     sketch code when exported.
# 1.0.1
#   - Fixed bug in names generated for lineoptions. Thanks Semmelb!
# 1.0.0 - First public release

# Ideas:
# - Export cameras
# - Flat shading. Should be implemented in Sketch, but is probably quite easy to
#   implement in the exporter as well.
# - Draw meshes with both faces and 'standalone' edges
# - FGONS support

import Blender
from Blender import sys as bsys
from Blender import Mesh, Mathutils, Registry, Scene, Material, Group
from Blender import Text

import string
from string import Template
# Start of configuration section -------

REG_KEY = 'sketch_export'

# config options:

EXPORT_MATERIALS = True
OUTPUT_LANGUAGE = 'tikz'
EXPORT_CAMERA = True
STANDALONE = True
USE_TWOSIDED_FLAG = True
SPE_LINE_COLOR = False
ONLY_PROPERTIES = False
EXPORT_GROUPS = True

GLOBAL_COORDS = True
TRANSFORM_VERTICES = True
CLIPBOARD_OUTPUT = False

USE_SKTEXT = True

tooltips = {
    #'GLOBAL_COORDS' : 'Transform mesh vertices to global coordianates',
    'EXPORT_MATERIALS' : 'Apply materials to mesh faces',
    'EXPORT_CAMERA' : "Set Sketch's camera view similar to Blender's",
    'EXPORT_GROUPS' : "Export groups",
    'OUTPUT_LANGUAGE' :
        'Set to tikz or pstricks. Output language generated by Sketch',
    'STANDALONE' : 'Output a standalone document',
    'USE_TWOSIDED_FLAG' :
        'Disable culling for objects with double sided option set',
    'SPE_LINE_COLOR' :
        'Use the specular color of the material as polygon line color',
    'ONLY_PROPERTIES' :
        'Use only properties for materials with the polyoptions and/or '\
        'lineoptions set',
    'CLIPBOARD_OUTPUT' :
        'Put generated code on clipboard',
    'USE_SKTEXT' :
        'Insert contents of the skcode text buffer in the generated code',
}

def update_registry():
    d = {
        'EXPORT_MATERIALS' : EXPORT_MATERIALS,
        'OUTPUT_LANGUAGE' : OUTPUT_LANGUAGE,
        'USE_TWOSIDED_FLAG' : USE_TWOSIDED_FLAG,
        'EXPORT_CAMERA' : EXPORT_CAMERA,
        'STANDALONE' : STANDALONE,
        'SPE_LINE_COLOR' : SPE_LINE_COLOR,
        'ONLY_PROPERTIES' : ONLY_PROPERTIES,
        'EXPORT_GROUPS' : EXPORT_GROUPS,
        'CLIPBOARD_OUTPUT' : CLIPBOARD_OUTPUT,
        'USE_SKTEXT' : USE_SKTEXT
    }
    Registry.SetKey(REG_KEY, d, True)

# Looking for a saved key in Blender.Registry dict:
rd = Registry.GetKey(REG_KEY, True)

if rd:
    try:
        OUTPUT_LANGUAGE = rd['OUTPUT_LANGUAGE']
        EXPORT_MATERIALS = rd['EXPORT_MATERIALS']
        USE_TWOSIDED_FLAG = rd['USE_TWOSIDED_FLAG']
        EXPORT_CAMERA = rd['EXPORT_CAMERA']
        STANDALONE = rd['STANDALONE']
        SPE_LINE_COLOR = rd['SPE_LINE_COLOR']
        ONLY_PROPERTIES = rd['ONLY_PROPERTIES']
        EXPORT_GROUPS = rd['EXPORT_GROUPS']
        CLIPBOARD_OUTPUT = rd['CLIPBOARD_OUTPUT']
        USE_SKTEXT = rd['USE_SKTEXT']
    except KeyError:
        print "Keyerror"
        update_registry()
else:
    update_registry()

# Templates ------------------------------------------------

axes_template = """
def $defname {
    def ix ($O)+[${O}_x]
    def iy ($O)+[${O}_y]
    def iz ($O)+[${O}_z]
    line[arrows=<->](ix)($O)(iy)
    line[arrows=->]($O)(iz)
    $labelcode
}
"""

daxes_template = """
def $defname {
    def ix ($O)+[${O}_x]
    def iy ($O)+[${O}_y]
    def iz ($O)+[${O}_z]
    line[arrows=<->](ix)($O)(iy)
    line[arrows=->]($O)(iz)
    % repeat dotted as an overlay
    line[lay=over,style=dotted,shorten >=2pt, shorten <=2pt](ix)($O)(iy)
    line[lay=over,style=dotted,shorten >=2pt]($O)(iz)
    $labelcode
}
"""

labelcode_template ="""\
special |\path[pos=$pos]
        #1 -- node {$xlbl} #2
        #1 -- node {$ylbl} #3
        #1 -- node {$zlbl} #4; | ($O)(ix)(iy)(iz)
"""

saxes_template = """
def $defname {
    def ix ($O)+[${O}_x]
    def iy ($O)+[${O}_y]
    def iz ($O)+[${O}_z]
    special |\path[->,pos=1.2]
        #1 edge node {$$x$$} #2
           edge node {$$y$$} #3
           edge node {$$z$$} #4; | ($O)(ix)(iy)(iz)
}
"""

# Start of GUI section ------------------------------------------------

from Blender import Draw

def draw_GUI():
    global EXPORT_MATERIALS, EXPORT_CAMERA, STANDALONE,USE_TWOSIDED_FLAG
    global OUTPUT_LANGUAGE, SPE_LINE_COLOR,ONLY_PROPERTIES, EXPORT_GROUPS
    global CLIPBOARD_OUTPUT
    global USE_SKTEXT
    format = Draw.Create(OUTPUT_LANGUAGE)
    mattog = Draw.Create(EXPORT_MATERIALS)
    cameratog = Draw.Create(EXPORT_CAMERA)
    groupstog = Draw.Create(EXPORT_GROUPS)
    standalonetog = Draw.Create(STANDALONE)
    doublesidedtog = Draw.Create(USE_TWOSIDED_FLAG)
    specoltog = Draw.Create(SPE_LINE_COLOR)
    onlyproptog = Draw.Create(ONLY_PROPERTIES)
    clipboardtog = Draw.Create(CLIPBOARD_OUTPUT)
    usesktexttog = Draw.Create(USE_SKTEXT)
    block = []

    block.append(("Output format: ", format, 0, 30,
        tooltips["OUTPUT_LANGUAGE"]))
    block.append("Export:")
    block.append(("Materials", mattog, tooltips['EXPORT_MATERIALS']))
    block.append(("Camera", cameratog, tooltips['EXPORT_CAMERA']))
    block.append(("Groups", groupstog, tooltips['EXPORT_GROUPS']))
    block.append("Material options:")
    block.append(("Spe line color",specoltog,tooltips['SPE_LINE_COLOR']))
    block.append(("Only properties",onlyproptog,tooltips['ONLY_PROPERTIES']))
    block.append('Ouput options')
    block.append(("Standalone", standalonetog, tooltips['STANDALONE']))
    block.append(("Clipboard", clipboardtog, tooltips['CLIPBOARD_OUTPUT']))
    block.append(("Insert skcode", usesktexttog, tooltips['USE_SKTEXT']))
    block.append("Other options:")
    block.append(("Double sided", doublesidedtog,
        tooltips['USE_TWOSIDED_FLAG']))

    retval = Blender.Draw.PupBlock("Blend2Sketch options", block)
    if retval:
        # set options
        if format in ['tikz','pstricks']:
            OUTPUT_LANGUAGE = format.val
        else:
            print "Invalid output format %s" % format
        EXPORT_MATERIALS = mattog.val
        USE_TWOSIDED_FLAG = doublesidedtog.val
        STANDALONE = standalonetog.val
        EXPORT_CAMERA = cameratog.val
        SPE_LINE_COLOR = specoltog.val
        ONLY_PROPERTIES = onlyproptog.val
        EXPORT_GROUPS = groupstog.val
        CLIPBOARD_OUTPUT = clipboardtog.val
        USE_SKTEXT = usesktexttog.val
        update_registry()
    return retval


# End of GUI section ----------------------

# End of configuration section ---------

used_materials = {}
drawables = []

def get_property(obj, name):
    """Get named object property

    Looks first in custom properties, then game properties. Returns a list.
    """
    prop_value = []
    try:
        prop_value.append(obj.properties[name])
    except:
        pass
    try:
        # look for game properties
        prop = obj.getProperty(name)
        if prop.type == "STRING" and prop.data.strip():
            prop_value.append(prop.data)
    except:
        pass
    return prop_value

def sketchify(name):
    """Return a valid Sketch identifier"""
    # From the sketch manual:
    # An identifier consists of a leading letter followed by letters, numbers
    # and underscores. The last character may not be an underscore. Keywords
    # cannot be used as identifiers, and reserved words ought to be avoided.
    tmp = name
    if not name[0] in string.ascii_letters:
        tmp = 'a'+tmp
    if tmp.endswith('_'):
        tmp += 'a'
    if name.lower() == 'camera':
        tmp += '_def'

    return tmp.replace('.','_')

def copy_to_clipboard(text):
    """Copy text to the clipboard

    Returns True if successful. False otherwise.

    Works on Windows, *nix and Mac. Tries the following:
    1. Use the win32clipboard module from the win32 package.
    2. Calls the xclip command line tool (*nix)
    3. Calls the pbcopy command line tool (Mac)
    4. Try pygtk
    """
    # try windows first
    try:
        import win32clipboard
        win32clipboard.OpenClipboard()
        win32clipboard.EmptyClipboard()
        win32clipboard.SetClipboardText(text)
        win32clipboard.CloseClipboard()
        return True
    except:
        pass
    # try xclip
    try:
        import subprocess
        p = subprocess.Popen(['xclip', '-selection', 'c'], stdin=subprocess.PIPE)
        p.stdin.write(text)
        p.stdin.close()
        retcode = p.wait()
        return True
    except:
        pass
    # try pbcopy (Os X)
    try:
        import subprocess
        p = subprocess.Popen(['pbcopy'], stdin=subprocess.PIPE)
        p.stdin.write(text)
        p.stdin.close()
        retcode = p.wait()
        return True
    except:
        pass
    # try os /linux
    try:
        import subprocess
        p = subprocess.Popen(['xsel'], stdin=subprocess.PIPE)
        p.stdin.write(text)
        p.stdin.close()
        retcode = p.wait()
        return True
    except:
        pass
    # try pygtk
    try:
        # Code from
        # http://www.vector-seven.com/2007/06/27/
        #    passing-data-between-gtk-applications-with-gtkclipboard/
        import pygtk
        pygtk.require('2.0')
        import gtk
        # get the clipboard
        clipboard = gtk.clipboard_get()
        # set the clipboard text data
        clipboard.set_text(text)
        # make our data available to other applications
        clipboard.store()
    except:
        pass
    # try clip (Vista)
    try:
        import subprocess
        p = subprocess.Popen(['clip'], stdin=subprocess.PIPE)
        p.stdin.write(text)
        p.stdin.close()
        retcode = p.wait()
        return True
    except:
        pass
    return False

def get_material(material, is_face):
    """Convert material to Sketch options"""
    if not material:
        return ""
    opts = ""
    mat_name = sketchify(material.name)
    if is_face:
        try:
            if material.properties['polyoptions']:
                opts = "%s_poly" % (mat_name)
        except:
             opts = "%s_poly" % mat_name
    else:
        try:
            if material.properties['lineoptions']:
                opts = "%s_line" % (mat_name)
        except:
             opts = "%s_line" % mat_name
    used_materials[mat_name] = material
    return opts

def write_empty(empty,name=''):
    """Generate code for empties"""
    s = ''
    if not name:
        empty_name = sketchify(empty.name)
    else:
        empty_name = name
    if empty.parent:
            # need to transform Empty to world coordinates
            #vec = Mathutils.Vector(obj.getLocation())
            #nvec = vec*obj.matrixWorld
            #x,y,z = nvec.x, nvec.y, nvec.z
            x,y,z = empty.getLocation('worldspace')
    else:
        x,y,z = empty.loc
    mtrx = empty.matrix.rotationPart()
    xvec,yvec,zvec = mtrx[0],mtrx[1],mtrx[2]
    empty_type = get_property(empty,'type')
    if empty_type:
        empty_type = empty_type[0]
    label = get_property(empty,'label')
    labels = get_property(empty,'labels')
    labelpos = get_property(empty,'pos')
    if labelpos:
        labelpos = labelpos[0]
    else:
        labelpos = "1.2"
    style = get_property(empty,'style')
    if style:
        styleopt = '[%s]' % style[0]
    else:
        styleopt = ''
    if labels:
        labels = labels[0].split('##')
        if len(labels) == 3:
            xlbl,ylbl,zlbl = labels
        else:
            xlbl, ylbl, zlbl = ("$x$","$y$","$z$")
        t = Template(labelcode_template)
        labelcode = t.substitute(xlbl=xlbl,ylbl=ylbl,zlbl=zlbl,O=empty_name,
                                 pos=labelpos)
    else:
        labelcode = ""

    s += "def %s (%s,%s,%s)\n" % (empty_name,x,y,z)
    s += 'def %s_x [%f,%f,%f]\n' % tuple([empty_name]+list(xvec))
    s += 'def %s_y [%f,%f,%f]\n' % tuple([empty_name]+list(yvec))
    s += 'def %s_z [%f,%f,%f]\n' % tuple([empty_name]+list(zvec))
    if label:
        texemptyname = "tex_%s" % empty_name
        s += "def %s {\n    special |\path #1 node%s {%s};|(%s)\n}\n\n" \
             % (texemptyname, styleopt,label[0], empty_name)
        drawables.append(texemptyname)

    elif empty_type == 'axes':
        pass
    defname = "%s_axes" % empty_name
    if empty_type == 'axes':
        t = Template(axes_template)
        s += t.substitute(defname=defname,O=empty_name,labelcode=labelcode)
        drawables.append(defname)
    elif empty_type == 'special axes':
        t = Template(saxes_template)
        s += t.substitute(defname=defname, O=empty_name, labelcode=labelcode)
        drawables.append(defname)
    elif empty_type == 'dotted axes':
        t = Template(daxes_template)
        s += t.substitute(defname=defname,O=empty_name, labelcode=labelcode)
        drawables.append(defname)
    return s

def write_camera(cam):
    if not cam.type == 'Camera':
        return ""
    s = ""
    vec = Mathutils.Vector([0,0,-1])
    direction = cam.matrix.rotationPart()*vec
    loc = cam.getLocation('worldspace')
    viewname = sketchify(cam.name)
    s += "def %s view((%f,%f,%f),(%f,%f,%f),[0,0,1])\n\n"\
         % tuple([viewname]+list(loc)+list(direction))
    return s

def write_object(obj):
    """Write drawables and empties"""
    s = ""
    name = obj.name
    defname = sketchify(obj.name)
    prop = obj.properties

    mtrx = obj.matrix.rotationPart()
    xvec,yvec,zvec = mtrx[0],mtrx[1],mtrx[2]
    x,y,z = obj.getLocation('worldspace')
    if obj.type == 'Empty':
        s += write_empty(obj,defname)
    elif obj.type == 'Camera':
        s += write_camera(obj)
    elif obj.type in ['Mesh','Curve','Surf','MBall']:
        is_curve = (obj.type=='Curve')
        is_mesh = (obj.type == 'Mesh')
        drawables.append(sketchify(name))

        # write mesh to file

        if TRANSFORM_VERTICES:
            # The transformation code is from the Blender Python API
            # documentation.
            mesh = Mesh.New()
            mesh.getFromObject(obj.name)
            verts = mesh.verts[:]
            mesh.transform(obj.matrix)
        else:
            if not is_mesh:
                mesh= Mesh.New()
                mesh.getFromObject(obj.name)
            else:
                # Ensure that we get a mesh of the Mesh type
                mesh = obj.getData(mesh=True)
        twosided = (mesh.mode & Mesh.Modes['TWOSIDED']) and USE_TWOSIDED_FLAG
        s += 'def %s_o (%f,%f,%f)\n' % (defname,x,y,z)
        s += 'def %s_x [%f,%f,%f]\n' % tuple([defname]+list(xvec))
        s += 'def %s_y [%f,%f,%f]\n' % tuple([defname]+list(yvec))
        s += 'def %s_z [%f,%f,%f]\n' % tuple([defname]+list(zvec))
        polyoptsname = "%s_polyopts" % defname
        polyoptions = []
        try:
            polyopts = prop['polyoptions']
            polyoptions.append(polyopts)
        except:
            pass
        if twosided:
            polyoptions.append('cull=false')
        if polyoptions:
            s += 'def %s [%s]\n' % (polyoptsname,",".join(polyoptions))
        else:
            polyoptsname = ""
        lineoptsname = ""
        try:
            lineopts = prop['lineoptions']
            lineoptsname = "%s_lineopts" % defname
            s += 'def %s [%s]\n' % (lineoptsname,lineopts)
        except:
            lineoptsname = ""
        s += 'def %s {\n' % (defname)

        if EXPORT_MATERIALS:
            if is_mesh:
                materials = mesh.materials
            else:
                try:
                    materials = obj.data.getMaterials()
                    # does not work with MBall
                except:
                    materials = []

        if len(mesh.faces) > 0:
            # iterate over all faces in the mesh


            for face in mesh.faces:
                polyoptions= []
                # each face is represented as a polygon
                if EXPORT_MATERIALS and len(materials):
                    polymatopts = get_material(materials[face.mat],is_face=True)
                    polyoptions.append(polymatopts)
                if polyoptsname:
                    polyoptions.append(polyoptsname)

                if polyoptions:
                    polyoptions = "[%s]" % ",".join(polyoptions)
                else:
                    polyoptions = ""
                s += '    polygon%s' % polyoptions
                # iterate over the vertices in the mesh
                for vert in face.v:
                    v = mesh.verts[vert.index]
                    s +=  '(%f,%f,%f)' % tuple(v.co)
                s += '\n'
        else:
            prev = None
            connectededges = False
            lineoptions = []
            if EXPORT_MATERIALS and materials:
                # pick first material
                for mat in materials:
                    if mat:
                        linematopts = get_material(mat, is_face=False)
                        lineoptions.append(linematopts)
                        break
            if lineoptsname:
                lineoptions.append(lineoptsname)
            if lineoptions:
                lineoptions = "[%s]" % ",".join(lineoptions)
            else:
                lineoptions = ""
            for edge in mesh.edges:
                if prev and (prev.v2.co == edge.v1.co):
                    connectededges = True
                    s += '(%f,%f,%f)' % tuple(edge.v2.co)
                else:
                    if connectededges:
                        s += '\n'
                        connectededges=False
                    s += '    line%s (%f,%f,%f)(%f,%f,%f)' % \
                       (lineoptions, edge.v1.co.x, edge.v1.co.y, edge.v1.co.z,
                        edge.v2.co.x, edge.v2.co.y, edge.v2.co.z)
                prev = edge
            s += '\n'


        if TRANSFORM_VERTICES:
            # Restore vertices.
            mesh.verts = verts
        s += '}\n\n'

    return s

def write_materials(used_materials):
    c = "% Materials section \n"
    for material in used_materials.values():
        mat_name = sketchify(material.name)
        polyopts = lineopts = opts = ''
        proponly = ONLY_PROPERTIES
        try:
            proponly = material.properties['onlyproperties']
            if proponly and type(proponly) == str:
                proponly =  proponly.lower() not in ('0','false')
        except:
            pass
        try:
            polyopts = material.properties['polyoptions']
        except:
            pass

        try:
            lineopts = material.properties['lineoptions']
        except:
            pass

        rgb = material.rgbCol
        spec = material.specCol
        alpha = material.alpha
        flags = material.getMode()
        if not (proponly and ( polyopts and lineopts )):
            c += "special |\definecolor{%s}{rgb}{%s,%s,%s}|[lay=under]\n" \
                % tuple([mat_name]+rgb)
            if SPE_LINE_COLOR:
                spemat_name = "%s_l" % mat_name
                c += "special |\definecolor{%s}{rgb}{%s,%s,%s}|[lay=under]\n" \
                % tuple([spemat_name]+spec)

            opts = "%s=%s" % (FILL_STR, mat_name)
            if SPE_LINE_COLOR:
                opts += ",%s=%s" % (LINE_STR, spemat_name)
            if alpha < 1.0:
                opts += ",%s=%s"  % (OPACITY_STR,alpha)

        if polyopts and opts and not proponly:
            opts += ',' + polyopts
        else:
            opts = polyopts or opts

        c += "def %s_poly [%s]\n" % (mat_name,opts)
        opts = ''
        if not (lineopts and proponly):
            opts = "%s=%s" % (LINE_STR, mat_name)

        if lineopts and opts:
            opts += ','+lineopts
        else:
            opts = lineopts or opts

        c += "def %s_line [%s]\n" % (mat_name,opts)
    return c


def write_objects(filepath):
    """Write all selected objects to filepath"""
    # get all selected objects
    objects = Blender.Object.GetSelected()
    if not CLIPBOARD_OUTPUT:
        f = file(filepath, 'w')
        # write header to file
        f.write('# Generated by sketch_export.py v %s \n' % (__version__))

    # Group objects
    grouped_objects = dict(Mesh=[],Empty=[],Camera=[])
    for obj in objects:
        if obj.type in ['Mesh','Curve','Surf','MBall']:
            grouped_objects['Mesh'].append(obj)
        elif obj.type == 'Empty':
            grouped_objects['Empty'].append(obj)
        elif obj.type == 'Camera':
            grouped_objects['Camera'].append(obj)

    # Look for active camera
    scn = Blender.Scene.GetCurrent()
    active_camera = scn.objects.camera

    if active_camera and active_camera not in grouped_objects['Camera']:
        grouped_objects['Camera'].append(active_camera)

    s = ""

    # Get name of scene
    scene_name = sketchify(scn.name)
    if grouped_objects['Mesh']:
        s += "% Mesh section\n\n"
        for obj in grouped_objects['Mesh']:
            s += write_object(obj)
        s += "\n"

    if grouped_objects['Empty']:
        s += "% Empty section\n\n"
        for obj in grouped_objects['Empty']:
            s += write_object(obj)
        s += "\n"

    if grouped_objects['Camera']:
        s += "% Camera section\n\n"
        for obj in grouped_objects['Camera']:
            s += write_object(obj)
        s += "\n"

    if used_materials:
        c = write_materials(used_materials)
        s = c+"\n"+s

    groups = Group.Get()
    c = ""
    if EXPORT_GROUPS and groups:
        s += "% Group section"
        for group in groups:
            objects = group.objects
            if objects:
                for obj in objects:
                    obj_name = sketchify(obj.name)
                    if obj_name in drawables:
                        c += "  {%s}\n" % obj_name
                if c:
                    group_name = sketchify(group.name)
                    s += "\ndef %s {\n%s}\n" % (group_name, c)
                    c = ""

    # Write scene to file or clipboard
    if drawables:
        s += "\ndef %s {\n" % scene_name
        if EXPORT_CAMERA and active_camera:
            viewname = sketchify(active_camera.name)
            s += "    put {[[%s]]}{\n" % viewname
        for name in drawables:
            s += "        {%s}\n" % name
        if USE_SKTEXT:
            try:
                code = Text.get('skcode_%s' % scene_name)
            except NameError:
                code = None
            if not code:
                try:
                    code = Text.Get('skcode')
                except NameError:
                    code = None
            if code:
                s += "        " + "\n        ".join(code.asLines()) +'\n'


        if EXPORT_CAMERA:
            s += "    }\n"
        s += "}\n"
        if STANDALONE:
            s += "\n"
            s += "{%s}\n" % scene_name
            if OUTPUT_LANGUAGE == 'tikz':
                s += "global {language tikz}\n"
    if not CLIPBOARD_OUTPUT:
        f.write(s)
        f.close()
    else:
        success = copy_to_clipboard(s)
        if not success:
            print "Failed to copy code to the clipboard"
            print "Pywin32, xclip, cbcopy, xsel, clip or pygtk required for clipboard support"
            Blender.Draw.PupMenu('ERROR: Failed to copy generated code to the clipboard')


# Start of script -----------------------------------------------------

# Ensure that at leas one object is selected
if len(Blender.Object.GetSelected()) == 0:
    # no objects selected. Print error message and quit
    Blender.Draw.PupMenu('ERROR: Please select at least one mesh')
else:
    fname = bsys.makename(ext=".sk")
    retval = draw_GUI()

    # Set TikZ and PSTricks specific parameters
    if OUTPUT_LANGUAGE == 'tikz':
        FILL_STR = "fill"
        LINE_STR = "draw"
        OPACITY_STR = "fill opacity"
    else:
        FILL_STR = "fillcolor"
        LINE_STR = "linecolor"
        OPACITY_STR = "fill opacity"
    if retval:
        if not CLIPBOARD_OUTPUT:
            Blender.Window.FileSelector(write_objects, "Export SK", fname)
        else:
            write_objects(fname)
        print "sketch_export ended ..."
